package base

import (
	"reflect"
	"sync"
	"time"

	"gopkg.in/mgo.v2"
	"strings"
	"gitee.com/dayu1985/can_api/controller/app"
)

// 原始数据库
type Raw struct {
	*MgoDatabase              `db:"raw"`
	STSwitch   *MgoCollection `json:"stswitch"`
	Histroy    *MgoCollection `json:"histroy"`
	Packet     *MgoCollection `json:"packet"`
	Efficiency *MgoCollection `json:"histroy_efficiency"`
}

// 系统日志
type AppLog struct {
	*MgoDatabase             `db:"applog"`
	API       *MgoCollection `json:"api" mgoIdx:"token;url;ip;result.Code;"` //mgoIdx索引建立标志
	RY        *MgoCollection `json:"ryhistory"`
	Login     *MgoCollection `json:"login"` //用户登录日志
	Exception *MgoCollection `json:"exception"`
}

// mongo 引擎
type Mongo struct {
	Prefix string  `json:"prefix"`
	Raw    *Raw    `json:"raw"`
	AppLog *AppLog `json:"applog"`
}

var afterInit []func()

func AfterInitMongo(fn func()) {
	afterInit = append(afterInit, fn)
}

// 初始化mongo引擎
func initMongo() {
	initMongoService()

	//初始化完成之后
	for _, fn := range afterInit {
		fn()
	}
}

func initMongoService() {
	Serve.Mgo = new(Mongo)
	prefix := app.MustValue("database", "mongodb.prefix", "")
	Serve.Mgo.Prefix = prefix

	//通过反射，将mongo Struct中的数据初始化
	ty := reflect.TypeOf(Serve.Mgo)
	vl := reflect.ValueOf(Serve.Mgo)
	ty = ty.Elem()
	vl = vl.Elem()
	//1. 第一层反射，反射出所有的库,并且new一个库
	for i := 0; i < ty.NumField(); i++ {
		t := ty.Field(i).Tag
		if t.Get("json") != "prefix" {

			//反射实际操作的库，然后初始化所有的collection
			cty := ty.Field(i).Type
			if cty.Kind() == reflect.Ptr {
				cty = cty.Elem()
			}
			// 1.1 实例化一个库
			dbObj := reflect.New(cty).Interface()
			vl.Field(i).Set(reflect.ValueOf(dbObj))

			// 1.2 检查库
			dbty := reflect.TypeOf(dbObj)
			dbvl := reflect.ValueOf(dbObj)
			if dbty.Kind() == reflect.Ptr {
				dbty = dbty.Elem()
				dbvl = dbvl.Elem()
			}
			if dbty.Kind() == reflect.Struct {
				var db *mgo.Database
				// 2.1 第二层反射，反射出所有库中的文档（collection），并且初始化
				// 2.2 2次for循环，第一次for循环只是初始化MgoDatabase
			j:
				for j := 0; j < dbty.NumField(); j++ {
					dbtag := dbty.Field(j).Tag
					dbstr := dbtag.Get("db")
					if dbty.Field(j).Name == "MgoDatabase" || dbty.Field(j).Name == "MgoFs" {
						mgodbs := reflect.New(dbty.Field(j).Type.Elem())
						dbvl.Field(j).Set(mgodbs)

						//继续反射MgoDatabase
						mdty := reflect.TypeOf(mgodbs.Interface())
						mdvl := reflect.ValueOf(mgodbs.Interface())
						if mdty.Kind() == reflect.Ptr {
							mdty = mdty.Elem()
							mdvl = mdvl.Elem()
						}
						// 最终反射，获取到实际的mgo.Database的对象，并且赋值到对应的属性中
						for k := 0; k < mdty.NumField(); k++ {
							if mdty.Field(k).Name == "Database" {
								db = Serve.registerMongo(dbstr)
								break j
							} else if mdty.Field(k).Name == "GridFS" {
								mdvl.Field(k).Set(reflect.ValueOf(Serve.registerMongo(dbstr).GridFS("fs")))
								break j
							}
						}
					}
				}

				// 2.3 第二次for循环，初始化MgoCollection
				for j := 0; j < dbty.NumField(); j++ {
					dbv := dbvl.Field(j)
					dbtag := dbty.Field(j).Tag
					if dbty.Field(j).Name != "MgoDatabase" && dbty.Field(j).Name != "MgoFs" {

						mgodbs := reflect.New(dbty.Field(j).Type.Elem())
						dbv.Set(mgodbs)

						//继续反射MgoCollection
						mdty := reflect.TypeOf(mgodbs.Interface())
						mdvl := reflect.ValueOf(mgodbs.Interface())
						if mdty.Kind() == reflect.Ptr {
							mdty = mdty.Elem()
							mdvl = mdvl.Elem()
						}
						// 最终反射，获取到实际的mgo.Database的对象，并且赋值到对应的属性中
						for k := 0; k < mdty.NumField(); k++ {
							if mdty.Field(k).Name == "Collection" {
								dbstr := dbtag.Get("json")
								col := db.C(dbstr)
								mdvl.Field(k).Set(reflect.ValueOf(col))
								mgoIndexGenerate(dbtag.Get("mgoIdx"), col)
								break
							}
						}

					}
				}
			}
		}
	}
}

func mgoIndexGenerate(str string, col *mgo.Collection) {
	if str == "" {
		return
	}
	strs := strings.Split(str, ";")
	for _, s := range strs {
		i := mgo.Index{
			Key:        strings.Split(s, ","),
			Unique:     false,
			DropDups:   true,
			Background: true,
			Sparse:     true,
		}
		col.EnsureIndex(i)
	}
}

//初始化mongo引擎
var mutex = sync.Mutex{}

func (this *Service) registerMongo(db string) *mgo.Database {
	mutex.Lock()
	defer mutex.Unlock()

	url := app.MustValue("database", "mongodb", "")
	if url == "" {
		panic("未定义数据库...")
	}
	sess, err := mgo.Dial(url)
	if err != nil {
		panic(err)
	}

	//保证连接可用性 - 断线自动重连
	go func(sess *mgo.Session) {
		for {
			select {
			case <-time.After(5 * time.Second):
				sess.Refresh()
			}
		}
	}(sess)
	println("db")
	println(this.Mgo.Prefix + db)
	return sess.DB(this.Mgo.Prefix + db)

}
